package nancal.mp.config.base

import nancal.mp.client.api
import nancal.mp.db.mongo.JenkinsLanguageEnum
import nancal.mp.db.mongo.ServiceNameStyleEnum
import nancal.mp.db.mongo.entity.dev.AppDeploySetting
import nancal.mp.db.mongo.entity.dev.GitData
import nancal.mp.db.mongo.entity.sys.ApiRouterDefine
import nancal.mp.db.mongo.entity.sys.GatewayRouteDefine
import nancal.mp.db.mongo.extend.getAppDeploySetting
import nancal.mp.db.mongo.mor
import nancal.mp.started
import nbcp.base.enums.LogLevelScopeEnum
import nbcp.base.extend.*
import nbcp.http.api.DockerClientApi
import nbcp.myoql.db.mongo.query
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cloud.client.discovery.DiscoveryClient
import org.springframework.cloud.gateway.filter.FilterDefinition
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition
import org.springframework.cloud.gateway.route.RouteDefinition
import org.springframework.cloud.gateway.route.RouteDefinitionRepository
import org.springframework.stereotype.Component
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.net.URI


/**
 * gateway 一级网关 nginx 绑定  (\w+)-api
 * 单集群:  api/zao-gatewy/url  ,  api/zao/url
 * 多集群: zao-api/url 是固定URL,有可配置以下网关
 *  app.sub-gateway: it-gateway
 */
@Component
class MyGatewayRouteRepository : RouteDefinitionRepository {

    companion object {
        private val logger = LoggerFactory.getLogger(this::class.java.declaringClass)
        var apiRoutes = listOf<ApiRouterDefine>()
        var gatewayRouteDefines = listOf<GatewayRouteDefine>()
        var gitDatas = listOf<GitData>()
        var apiTags = listOf("openjdk", "jdk", "-java", "-api", "-backend")
        var appSetting = AppDeploySetting();
    }

    data class ServiceDefineModel(
        var name: String = "",

        //不带 http://
        var targetServiceName: String = ""
    ) {

        fun toRouteDefine(): RouteDefinition? {
            if (gatewayRouteDefines.any { define -> define.isBlock && this.name == define.name }) {
                return null
            }

            var name2 = name.replace("-", "_")
            if (targetServiceName.isNullOrEmpty()) {
                return null
            }

            if (targetServiceName.startsWith("http://") && targetServiceName.length == 7) {
                return null
            }

            if (targetServiceName.startsWith("https://") && targetServiceName.length == 8) {
                return null
            }

            var targetUrl = "http://$targetServiceName"

            var define = RouteDefinition();
            define.uri = URI.create(targetUrl)
            define.id = "id_" + name2;

            var predicate = PredicateDefinition();
            predicate.name = "Path";
            predicate.addArg("Path", "/${name}/**")
            define.predicates.add(predicate)

            var filterDefine = FilterDefinition()
            filterDefine.addArg("StripPrefix", "1")
            filterDefine.name = "StripPrefix"
            define.filters.add(filterDefine);

            return define
        }
    }

    var dockerRoutes = listOf<DockerClientApi.DockerContainer>()

    fun loadDockerServices() {
        dockerRoutes = (api.sys.docker?.containers() ?: listOf())
            .filter { container ->
                apiTags.any { container.Image.contains(it, true) }
            }
    }

    fun loadAppDefinesFromDb() {
        appSetting = mor.dev.appDeploySetting.getAppDeploySetting();
        gitDatas = mor.dev.gitData.query()
            .where { it.language mongoIn JenkinsLanguageEnum.getServerLanguage() }
            .where { it.isLib mongoNotEquals true }
            .toList()

        gitDatas.forEach { gitData ->
            gitData.folders.removeAll { it.name.isNullOrEmpty() || it.name == "." }
        }
    }

    @Autowired
    lateinit var discoveryClient: DiscoveryClient;


    fun GitData.toRouteDefinitions(): List<ServiceDefineModel> {
        var cache = mutableListOf<ServiceDefineModel>()

        this.folders.forEach { folder ->
            var path = folder.name
            var sect_name = path.split("-").filter { it.HasValue }


            if (discoveryClient.services.contains(folder.name)) {
                return@forEach;
            }


            var targetUrl = path;


            if (appSetting.serviceNameStyle.IsInItems(
                    ServiceNameStyleEnum.ProductLineCode,
                    ServiceNameStyleEnum.ProductLineCodeLanguage
                )
            ) {
                var productLineCode = this.productLine.code;
                if (productLineCode.HasValue && productLineCode != "-") {
                    targetUrl = productLineCode + "-" + path;
                }
            }


            if (appSetting.serviceNameStyle.IsInItems(
                    ServiceNameStyleEnum.Language,
                    ServiceNameStyleEnum.ProductLineCodeLanguage
                )
            ) {
                targetUrl += "-${this.language.toString().lowercase()}";
            }



            if (sect_name.size > 1 && sect_name.last() == "gateway") {
                cache.add(
                    ServiceDefineModel(
                        path,
                        targetUrl
                    )
                )

                var shortName = sect_name.Slice(0, -1).joinToString("-")
                cache.add(
                    ServiceDefineModel(
                        shortName,
                        targetUrl
                    )
                )

                return@forEach
            }

            cache.add(ServiceDefineModel(path, targetUrl))
        }

        return cache.distinctBy { it.name };
    }


    fun GatewayRouteDefine.toRouteDefinition(): ServiceDefineModel? {
        if (this.target.isNullOrEmpty() || this.isBlock) {
            throw RuntimeException("不允许加载非法定义！")
        }

        if (discoveryClient.services.contains(this.name)) {
            return null
        }

        return ServiceDefineModel(this.name, this.target)
    }

    override fun getRouteDefinitions(): Flux<RouteDefinition> {
        if (!started) {
            return Flux.just();
        }

        usingScope(LogLevelScopeEnum.ERROR) {
            loadAppDefinesFromDb();
            loadDockerServices();
            loadApiRouteFromDb();
            loadGatewayRouteDefineFromDb();
        }

        var result = loadDefines();
        return Flux.just(*result.toTypedArray());
    }

    private fun loadGatewayRouteDefineFromDb() {
        gatewayRouteDefines = mor.sys.gatewayRouteDefine.query()
            .toList()
    }

    private fun loadApiRouteFromDb() {
        apiRoutes = mor.sys.apiRouterDefine.query().toList()
    }

    private fun loadDefines(): List<RouteDefinition> {
        var routes = dockerRoutes
            .map { ServiceDefineModel(it.containerName, it.containerIp) }



        routes += gitDatas
            .map { it.toRouteDefinitions() }
            .Unwind()


        routes += gatewayRouteDefines
            .filter { !it.isBlock && it.target.HasValue }
            .map { it.toRouteDefinition() }
            .filter { it != null }
            .map { it!! }


        var ret = routes
            .filter { it.targetServiceName.HasValue }
            .map { it.toRouteDefine() }
            .filter { it != null }
            .map { it!! }
            .toMutableList()


        //再加上转发
//        ret.addAll(this.apiRoutes.map { getApiRoute(it) }.filter { it.id.HasValue })

        return ret;
    }

//    private fun getApiRoute(
//            apiRouter: ApiRouterDefine,
//    ): RouteDefinition {
//        var define = RouteDefinition()
//        if (apiRouter.uri.isEmpty()) {
//            return define
//        }
//
//        define.id = "api_" + apiRouter.id;
//
//        define.uri = URI.create(apiRouter.uri)
//        var predicate = PredicateDefinition();
//        predicate.name = "Path";
//        predicate.addArg("oldPath", apiRouter.path)
//        define.predicates.add(predicate)
//
//        if (apiRouter.whenHost.HasValue) {
//            var predicate = PredicateDefinition();
//            predicate.name = "Host";
//            predicate.addArg("Host", apiRouter.whenHost)
//            define.predicates.add(predicate)
//        }
//
//        if (apiRouter.whenHeader.HasValue) {
//            var predicate = PredicateDefinition();
//            predicate.name = "Header";
//            predicate.addArg("Header", apiRouter.whenHeader)
//            define.predicates.add(predicate)
//        }
//
//
//        var filter = FilterDefinition();
//        filter.name = "RewritePath"
//        filter.addArg("_genkey_0", apiRouter.oldPath)
//        filter.addArg("_genkey_1", apiRouter.newPath)
//        define.filters.add(filter)
//        return define;
//    }


    override fun save(route: Mono<RouteDefinition>): Mono<Void> {
        throw RuntimeException("不支持！")
    }

    override fun delete(routeId: Mono<String>): Mono<Void> {
        throw RuntimeException("不支持！")
    }
}